﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.ServiceModel;
using System.Web;
using System.Web.Caching;
using Contracts.ClientTools;
using Contracts.ServiceContracts.ClientCache;
using Types.Constants;
using Types.Enumerations;

namespace Contracts.Caching
{
	internal class FileDependentCacheProvider : ICacheProvider
	{
		public static FileDependentCacheProvider GetProviderInstance
		{
			get { return new FileDependentCacheProvider(); }
		}

		private static Hashtable ht;

		private static List<string> mWebServersInFarm;

		static FileDependentCacheProvider()
		{
			ht = new Hashtable();
			var lastCacheName = string.Empty;
			try
			{
				foreach (CacheItem ci in CacheProvider.ProviderSettings.CacheItems)
				{
					ht.Add(ci.CacheItemName, ci);
					lastCacheName = ci.CacheItemName;
				}
			}
			catch (Exception ex)
			{
				EventLog.WriteEntry(CommonConstants.EventLogSource.Application, "Dublicated Cache Item: '" + lastCacheName + "'",
					EventLogEntryType.Error, CacheEventLogId.DoublicatedCacheItem);
				throw new ApplicationException("Dublicated Cache Item: '" + lastCacheName + "'", ex.InnerException);
			}
		}

		private static void InsertCache(string key, CacheItem pCacheItem)
		{
			DataSet ds = null;

			if (pCacheItem.CacheType == EnumCacheType.ClientCache)
			{
				ds = ServiceProxyCreator<IClientCache, DataSet>.CallService(proxy =>
				{
					return proxy.GetCacheItemWithRefresh(key);
				}
				);

			}
			else
			{
				Assembly asm = Assembly.GetAssembly(Type.GetType("Services.BusinessObjects.ClientCache.ClientCacheBS, Services"));

				if (pCacheItem.InvokeDirectly)
				{
					try
					{
						Type bsType = asm.GetType("Services.BusinessObjects.ClientCache.BSServiceCacheHelper");
						object mBSServiceCacheHelper = bsType.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
						ds = (DataSet)bsType.InvokeMember("HelpTableCache", BindingFlags.InvokeMethod, null, mBSServiceCacheHelper
							, new object[] { pCacheItem.TypedDsName });
					}
					catch (TargetInvocationException tEx)
					{
						EventLog.WriteEntry(CommonConstants.EventLogSource.Application, tEx.ToString(), EventLogEntryType.Error, CacheEventLogId.CacheError);
						if (tEx.InnerException != null)
						{
							throw new FaultException(new FaultReason(tEx.InnerException.Message));
						}
						else
						{
							throw new FaultException(new FaultReason(tEx.Message));
						}
					}
					catch (Exception ex)
					{
						EventLog.WriteEntry(CommonConstants.EventLogSource.Application, ex.Message, EventLogEntryType.Error, CacheEventLogId.CacheError);
						throw new FaultException(new FaultReason(ex.Message));
					}
				}
				else
				{
					try
					{
						foreach (Type t in asm.GetTypes())
						{
							if (t.GetInterface(pCacheItem.Interfacename.FullName) != null)
							{
								object mInstance = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
								ds = (DataSet)pCacheItem.Interfacename.GetMethod(pCacheItem.MethodName).Invoke(mInstance, null);
								break;
							}
						}
					}
					catch (TargetInvocationException tEx)
					{
						EventLog.WriteEntry(CommonConstants.EventLogSource.Application, tEx.ToString(), EventLogEntryType.Error, CacheEventLogId.CacheError);
						if (tEx.InnerException != null)
						{
							throw new FaultException(new FaultReason(tEx.InnerException.Message));
						}
						else
						{
							throw new FaultException(new FaultReason(tEx.Message));
						}
					}
					catch (Exception exc)
					{
						EventLog.WriteEntry(CommonConstants.EventLogSource.Application, exc.Message, EventLogEntryType.Error, CacheEventLogId.CacheError);
						throw new FaultException(new FaultReason(exc.Message));
					}
				}
			}


			string mFilename = string.Format(@"{0}\{1}.txt", pCacheItem.FileDependencyFolder, pCacheItem.CacheItemName);

			// If there is no file then create it
			// File IO permission required. For this path, IIS service account should be authorized
			if (!System.IO.File.Exists(mFilename))
			{
				//Always creating file. 
				using (System.IO.StreamWriter sw = new System.IO.StreamWriter(mFilename, false))
				{
					sw.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss:fff"));
					sw.Close();
				}
			}

			CacheDependency cdep = new CacheDependency(pCacheItem.FileDependencyFolder + @"\" + pCacheItem.CacheItemName + ".txt");

			DateTime expiration = Cache.NoAbsoluteExpiration;

			CacheItemRemovedCallback RemoveCallback = new CacheItemRemovedCallback(RemovedCallback);

			// If expirationInMinutes <= 0 then does not expire .... 
			if (pCacheItem.MinutesToExpire <= 0)
			{
				HttpRuntime.Cache.Insert(key, ds, cdep, expiration,
				 Cache.NoSlidingExpiration,
				 CacheItemPriority.NotRemovable,
				 RemoveCallback);

				EventLog.WriteEntry(CommonConstants.EventLogSource.Application, "Cache Insert Sonsuz: " + key, EventLogEntryType.Information, CacheEventLogId.CacheFilled);
			}
			else
			{
				HttpRuntime.Cache.Insert(key, ds, cdep, DateTime.Now.AddMinutes(pCacheItem.MinutesToExpire), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, RemoveCallback);
				EventLog.WriteEntry(CommonConstants.EventLogSource.Application, "Cache Insert Sureli: " + key, EventLogEntryType.Information, CacheEventLogId.CacheFilled);
			}
		}

		public static void RemovedCallback(String k, Object v, CacheItemRemovedReason r)
		{
			Debug.WriteLine(k + " cacheten düştü..." + r.ToString());

			if ((((CacheItem)ht[k]).CacheType == EnumCacheType.ClientCache) &&
				(((CacheItem)ht[k]).RefreshMethod == EnumFileDependentCacheRefreshMethod.PushViaASPX))
			{
				// Client broadcast ... 
				BroadCastToOtherWebServers(k);
			}

		}

		public static DataSet StaticGetCacheItem(string key)
		{
			DataSet ds = (DataSet)HttpRuntime.Cache[key];

			if (ds != null)
			{
				return ds;
			}

			if ((ds == null) && (ht.ContainsKey(key)))
			{
				lock ((CacheItem)ht[key])
				{
					ds = (DataSet)HttpRuntime.Cache[key];

					if (ds != null)
					{
						return ds;
					}
					CacheItem ci = (CacheItem)ht[key];
					InsertCache(key, ci);
					ds = (DataSet)HttpRuntime.Cache[key];

					return ds;
				}
			}

			return null;
		}

		public static void StaticRefreshCacheItem(string key)
		{


			// fileDependency path is monitored with DFS. 
			// Any change in any of the servers will be distributed and all caches with the specific key will be removed. 
			// New date will be written. Note: Another unique key may be used (forex.: guid)
			// File content will be deleted and new line will be written

			// If any UNC (Universal Naming Convention) path will be monitored instead of DFS (Distrubuted File System), single file syncronization should be made. 

			// Item not in cache... Just return. 

			DataSet ds = (DataSet)HttpRuntime.Cache[key];
			if (ds == null)
				return;

			if (((CacheItem)ht[key]).CacheType == EnumCacheType.ClientCache)
			{
				Debug.WriteLine("Client StaticRefreshCacheItem ......");
				ServiceProxyCreator<IClientCache>.CallService(proxy => proxy.GetCacheItemWithRefresh(key));
				HttpRuntime.Cache.Remove(key);
			}
			else
			{
				Debug.WriteLine("Server StaticRefreshCacheItem ......");

				lock (ht[key])
				{
					ds = (DataSet)HttpRuntime.Cache[key];
					if (ds == null)
						return;

					CacheItem ci = (CacheItem)ht[key];
					string mFilename = ci.FileDependencyFolder + @"\" + ci.CacheItemName + ".txt";
					EventLog.WriteEntry(CommonConstants.EventLogSource.Application, "2.file name = " + mFilename);
					// If there is no file, it means that cache has not initiated yet

					if (System.IO.File.Exists(mFilename))
					{
						// While we write file, anybody cannot read or write
						using (System.IO.FileStream f = System.IO.File.Open(mFilename, System.IO.FileMode.Truncate, System.IO.FileAccess.Write, System.IO.FileShare.None))
						{
							using (System.IO.StreamWriter sw = new System.IO.StreamWriter(f))
							{
								sw.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss:fff"));
								sw.Close();
							}
							f.Close();
						}
						HttpRuntime.Cache.Remove(key);
					}
				}
			}
		}

		private static void BroadCastToOtherWebServers(string pKey)
		{
			if (mWebServersInFarm == null)
			{
				mWebServersInFarm = new List<string>(ConfigurationManager.AppSettings["WebServersInFarm"].Split(';'));

				string sHostName = System.Net.Dns.GetHostName();
				mWebServersInFarm.Remove(sHostName);
			}

			if (mWebServersInFarm.Count <= 0)
				return;

			HttpWebRequest req;
			for (int i = 0; i < mWebServersInFarm.Count; i++)
			{
				string path = "http://";

				if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["IsHTTPS"])
									   && ConfigurationManager.AppSettings["IsHTTPS"] == "true") path = "https://";

				path = string.Format("{0}{1}{2}{3}", path, mWebServersInFarm[i], ConfigurationManager.AppSettings["FileDependedCacheClearPath"], pKey);
				req = (HttpWebRequest)WebRequest.Create(path);
				req.BeginGetResponse(new AsyncCallback(CallbackMethod), req);
			}
		}

		private static void CallbackMethod(IAsyncResult result)
		{
			HttpWebRequest req = (HttpWebRequest)result.AsyncState;
			try
			{
				req.EndGetResponse(result);

				EventLog.WriteEntry(CommonConstants.EventLogSource.Application, string.Format("BroadCast SUCCESSFUL ({0}) ...", req.RequestUri.AbsoluteUri));
			}
			catch (Exception ex)
			{
				EventLog.WriteEntry(CommonConstants.EventLogSource.Application, string.Format("BroadCast FAILED ({0}) ... Exception Details: {1}", req.RequestUri.AbsoluteUri, ex.Message));
			}
		}

		[System.ComponentModel.Description(CommonConstants.Messages.MethodIsInternal)]
		public static void StaticRemoveLocalCacheItem(string pKey)
		{
			if (((CacheItem)ht[pKey]).CacheType == EnumCacheType.ClientCache)
			{
				HttpRuntime.Cache.Remove(pKey);
				ServiceProxyCreator<IClientCache>.CallService(proxy => proxy.RemoveCacheItem(pKey));
			}
			else
			{
				HttpRuntime.Cache.Remove(pKey);
			}
		}

		public static bool StaticHasCacheItem(string key)
		{
			if (((DataSet)HttpRuntime.Cache[key]) != null) return true; else return false;
		}

		#region ICacheProvider Members

		public T GetCacheItem<T>(string key) where T : System.Data.DataSet
		{
			T ds = (T)FileDependentCacheProvider.StaticGetCacheItem(key);
			return ds;
		}

		public DataSet GetCacheItem(string key)
		{
			return FileDependentCacheProvider.StaticGetCacheItem(key);
		}

		public void RefreshCacheItem(string key)
		{
			FileDependentCacheProvider.StaticRefreshCacheItem(key);
		}

		public void RemoveCacheItem(string key)
		{
			FileDependentCacheProvider.StaticRemoveLocalCacheItem(key);
		}

		public bool HasCacheItem(string key)
		{
			return StaticHasCacheItem(key);
		}

		public DataSet GetCacheItemIfNewer(string key)
		{
			throw new NotImplementedException();
		}


		#endregion
	}
}
